package csu.softwaremetric.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

@Service
public class CKMetricService {
    private HashMap<String, MyClass> classMap;
    private HashMap<String, String> typeMap;
    private HashSet<String> attributeSet;
    private HashSet<String> methodSet;


    public CKMetricService() {
        classMap = new HashMap<>();
        typeMap = new HashMap<>();
        attributeSet = new HashSet<>();
        methodSet = new HashSet<>();
    }

    public JSONArray CKMetricALL(String path) {
        if(doParse(path) == 1) {
            for (Map.Entry<String, MyClass> stringMyClassEntry : classMap.entrySet()) {
                MyClass myClass = stringMyClassEntry.getValue();
                CKMetric(myClass);
            }

            for (Map.Entry<String, MyClass> stringMyClassEntry : classMap.entrySet()) {
                MyClass myClass = stringMyClassEntry.getValue();
                if (myClass.getParent() == null) {
                    calculateDIT(myClass);
                }
            }

            JSONArray jsonArray = new JSONArray();
            for (Map.Entry<String, MyClass> myClassEntry : classMap.entrySet()) {
                String key = myClassEntry.getKey();
                MyClass value = myClassEntry.getValue();

                JSONObject jsonObject = new JSONObject();
                jsonObject.put("name", key);
                jsonObject.put("wmc", value.getWMC());
                jsonObject.put("rfc", value.getRFC());
                jsonObject.put("dit", value.getDIT());
                jsonObject.put("noc", value.getNOC());
                jsonObject.put("cbo", value.getCBO());
                jsonObject.put("lcom", value.getLCOM());

                jsonArray.add(jsonObject);
            }
            return jsonArray;
        } else{
            return null;
        }
    }

    private void calculateDIT(MyClass myClass) {
        ArrayList<MyClass> list = myClass.getChildren();
        if (list != null && !list.isEmpty()) {
            for (MyClass aClass : list) {
                aClass.setDIT(myClass.getDIT() + 1);
                calculateDIT(aClass);
            }
        }
    }

    private void CKMetric(MyClass myClass) {
        //计算WMC
        myClass.setWMC(myClass.getMethods().size());

        //计算RFC
        HashSet<String> calculatedMethods = new HashSet<>();
        for (Map.Entry<String, String> entry : myClass.getMethods().entrySet()) {
            String name = entry.getKey();
            calculatedMethods.add(name);
            myClass.increaseRFC();
        }

        for (Map.Entry<String, String> entry : myClass.getMethods().entrySet()) {
            String body = entry.getValue();
            for (String method : methodSet) {
                if (body.contains(method) && !calculatedMethods.contains(method)) {
                    myClass.increaseRFC();
                    calculatedMethods.add(method);
                }
            }
        }

        //NOC初始化的时候就算过了，DIT在calculateDIT函数中计算
        //计算CBO
        myClass.setCBO(myClass.getAssociationClass().size() + myClass.getInterfaceClass().size());

        //计算LCOM
        ArrayList<Method> methods = new ArrayList<>();
        ArrayList<String> attributes = myClass.getAttributes();
        for (Map.Entry<String, String> entry : myClass.getMethods().entrySet()) {
            String name = entry.getKey();
            String body = entry.getValue();
            Method method = new Method(name, body);
            methods.add(method);
        }
        int P = 0, Q = 0;
        for (int i = 0; i < methods.size(); i++) {
            Method method1 = methods.get(i);
            for (int j = i + 1; j < methods.size(); j++) {
                Method method2 = methods.get(j);
                HashSet<String> commonAttributesSet = new HashSet<>();
                for (String attribute : attributes) {
                    if (method1.getBody().contains(attribute) && method2.getBody().contains(attribute)) {
                        commonAttributesSet.add(attribute);
                    }
                }
                if (commonAttributesSet.isEmpty()) {
                    P++;
                } else {
                    Q++;
                }
            }
        }
        myClass.setLCOM(P > Q ? P - Q : 0);

    }

    private int doParse(String path) {
        //1-获取XML-IO流
        InputStream xmlInputStream = getXmlInputStream(path);
        //2-解析XML-IO流 ，获取Document 对象，以及Document对象 的根节点
        Element rootElement;
        try {
            rootElement = getRootElementFromIs(xmlInputStream);
            //3~5-从根元素解析得到元素
            if(rootElement == null)
                return 0;
            parseElementFromRoot(rootElement);
        } catch (Exception e) {
            return 0;
        }
        return 1;
    }

    //1-获取XML-IO流
    private InputStream getXmlInputStream(String xmlPath) {
        InputStream inputStream = null;
        try {
            //1-把要解析的 XML 文档转化为输入流，以便 DOM 解析器解析它
            inputStream = new FileInputStream(xmlPath);
        } catch (Exception e) {
            return null;
        }
        return inputStream;
    }

    //2-解析XML-IO流 ，获取Document 对象，以及Document对象 的根节点
    private Element getRootElementFromIs(InputStream inputStream) throws Exception {
        if (inputStream == null) {
            return null;
        }
        /*
         * javax.xml.parsers 包中的DocumentBuilderFactory用于创建DOM模式的解析器对象 ，
         * DocumentBuilderFactory是一个抽象工厂类，它不能直接实例化，但该类提供了一个newInstance方法 ，
         * 这个方法会根据本地平台默认安装的解析器，自动创建一个工厂的对象并返回。
         */
        //2-调用 DocumentBuilderFactory.newInstance() 方法得到创建 DOM 解析器的工厂
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //3-调用工厂对象的 newDocumentBuilder方法得到 DOM 解析器对象。
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        //4-调用 DOM 解析器对象的 parse() 方法解析 XML 文档，得到代表整个文档的 Document 对象，进行可以利用DOM特性对整个XML文档进行操作了。
        Document doc = docBuilder.parse(inputStream);
        //5-得到 XML 文档的根节点
        Element root = doc.getDocumentElement();
        //6-关闭流
        inputStream.close();
        return root;
    }

    //3-从根元素解析得到元素
    private void parseElementFromRoot(Element root) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                //4-从元素解析得到属性值
                MyClass myClass = getDataFromElement(ele);
                //5-从元素解析特定子元素并解析(以property为例)
                if (myClass != null)
                    getCertainElementFromParentElement(ele, myClass);
            }
        }
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (ele.getAttribute("xmi:type").equals("uml:Class")) {
                    String name = ele.getAttribute("name");//根据属性名称读取属性值
                    getInherit(ele, classMap.get(name));
                }
            }
        }
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                getAssociationAndInterface(ele);
            }
        }
    }

    //4-从元素解析得到属性值
    private MyClass getDataFromElement(Element ele) {
        if (ele.getAttribute("xmi:type").equals("uml:Class")) {
            String name = ele.getAttribute("name");//根据属性名称读取属性值
            String type = ele.getAttribute("xmi:id");
            MyClass myClass = new MyClass(name);
            classMap.put(name, myClass);
            typeMap.put(type, name);
            return myClass;
        }
        return null;
    }

    private void getAssociationAndInterface(Element ele) {
        if (ele.getAttribute("xmi:type").equals("uml:Association")) {
            NodeList propertyEleList = ele.getElementsByTagName("ownedEnd");//根据标签名称获取标签元素列表
            MyClass[] myClasses = new MyClass[2];
            for (int i = 0; i < propertyEleList.getLength(); i++) {
                Node node = propertyEleList.item(i);
                if (node instanceof Element) {
                    Element propertyEle = (Element) node;
                    String type = propertyEle.getAttribute("type");
                    myClasses[i] = classMap.get(typeMap.get(type));
                }
            }
            myClasses[0].getAssociationClass().add(myClasses[1]);
            myClasses[1].getAssociationClass().add(myClasses[0]);

        } else if (ele.getAttribute("xmi:type").equals("uml:InterfaceRealization")) {
            Element element1 = (Element) ele.getElementsByTagName("ownedComment").item(0);
            Element element2 = (Element) element1.getElementsByTagName("body").item(0);
            String[] s = element2.getTextContent().split(" implements ");

            MyClass myClass = classMap.get(s[0]);
            myClass.getInterfaceClass().add(s[1]);
        }
    }


    //5-从元素解析特定子元素并解析(以property为例)
    private void getCertainElementFromParentElement(Element ele, MyClass myClass) {
        NodeList propertyEleList = ele.getElementsByTagName("ownedAttribute");//根据标签名称获取标签元素列表
        for (int i = 0; i < propertyEleList.getLength(); i++) {
            Node node = propertyEleList.item(i);
            if (node instanceof Element) {
                Element propertyEle = (Element) node;
                String name = propertyEle.getAttribute("name");
                myClass.getAttributes().add(name);
                attributeSet.add(name);
            }
        }

        propertyEleList = ele.getElementsByTagName("ownedOperation");//根据标签名称获取标签元素列表
        for (int i = 0; i < propertyEleList.getLength(); i++) {
            Node node = propertyEleList.item(i);
            if (node instanceof Element) {
                Element propertyEle = (Element) node;
                String name = propertyEle.getAttribute("name");
                Element element1 = (Element) propertyEle.getElementsByTagName("ownedRule").item(0);
                Element element2 = (Element) propertyEle.getElementsByTagName("specification").item(0);
                String body = element2.getAttribute("value");

                //为了解决重载方法，重载的方法后面会加上代号
                if (myClass.getMethods().containsKey(name)) {
                    name += propertyEle.getAttribute("xmi:id");
                }
                myClass.getMethods().put(name, body);
                methodSet.add(name);
            }
        }
    }

    private void getInherit(Element ele, MyClass myClass) {
        NodeList propertyEleList = ele.getElementsByTagName("generalization");//根据标签名称获取标签元素列表
        for (int i = 0; i < propertyEleList.getLength(); i++) {
            Node node = propertyEleList.item(i);
            if (node instanceof Element) {
                Element propertyEle = (Element) node;
                Element element1 = (Element) propertyEle.getElementsByTagName("ownedComment").item(0);
                Element element2 = (Element) element1.getElementsByTagName("body").item(0);
                String[] s = element2.getTextContent().split(" extends ");

                classMap.get(s[1]).increaseNOC();
                classMap.get(s[1]).getChildren().add(classMap.get(s[0]));

                classMap.get(s[0]).setParent(classMap.get(s[1]));
            }
        }
    }
}


class MyClass
{
    private final String name;

    private int WMC;
    private int RFC;
    private int DIT;
    private int NOC;
    private int CBO;
    private int LCOM;

    private ArrayList<String> attributes;
    private HashMap<String, String> methods;

    private ArrayList<MyClass> children;
    private MyClass parent;

    private HashSet<MyClass> associationClass;
    private HashSet<String> interfaceClass;

    public HashSet<String> getInterfaceClass() {
        return interfaceClass;
    }

    public HashSet<MyClass> getAssociationClass() {
        return associationClass;
    }

    public MyClass getParent() {
        return parent;
    }

    public void setParent(MyClass parent) {
        this.parent = parent;
    }

    public ArrayList<MyClass> getChildren() {
        return children;
    }

    public String getName() {
        return name;
    }

    public ArrayList<String> getAttributes() {
        return attributes;
    }

    public HashMap<String, String> getMethods() {
        return methods;
    }

    public int getWMC() {
        return WMC;
    }

    public void setWMC(int WMC) {
        this.WMC = WMC;
    }

    public int getRFC() {
        return RFC;
    }

    public void setRFC(int RFC) {
        this.RFC = RFC;
    }

    public void increaseRFC()
    {
        RFC++;
    }

    public int getNOC() {
        return NOC;
    }

    public void setNOC(int NOC) {
        this.NOC = NOC;
    }

    public void increaseNOC()
    {
        NOC++;
    }

    public int getDIT() {
        return DIT;
    }

    public void setDIT(int DIT) {
        this.DIT = DIT;
    }

    public int getCBO() {
        return CBO;
    }

    public void setCBO(int CBO) {
        this.CBO = CBO;
    }

    public int getLCOM() {
        return LCOM;
    }

    public void setLCOM(int LCOM) {
        this.LCOM = LCOM;
    }

    public MyClass(String name)
    {
        this.name = name;
        attributes = new ArrayList<>();
        methods = new HashMap<>();
        children = new ArrayList<>();
        associationClass = new HashSet<>();
        interfaceClass = new HashSet<>();
        parent = null;
        WMC = 0;
        RFC = 0;
        DIT = 0;
        NOC = 0;
        CBO = 0;
        LCOM = 0;
    }
}

class Method
{
    private String name;
    private String body;

    public Method(String name, String body) {
        this.name = name;
        this.body = body;
    }

    public String getName() {
        return name;
    }

    public String getBody() {
        return body;
    }
}
